﻿//     Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IdentityModel.Services;
using System.IdentityModel.Services.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Xml.Linq;
using Gateway.Console.Models;
using Gateway.Console.Services;
using Microsoft.Gateway.Utils;
using Microsoft.Gateway.Utils.Update;

namespace Gateway.Console.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            if (!Configuration.IsAppInitialized)
            {
                return RedirectToAction("Start");
            }
            return View();
        }

        [HttpGet]
        public async Task<ActionResult> IsConsoleUpdateAvailable()
        {
            try
            {
                var availableUpdates = await GetAvailableUpdates();
                return Json(new
                {
                    AvailableUpdate = availableUpdates.Any(),
                    HighestServerify = availableUpdates.Max(manifest => manifest.Severity).ToString(),
                    UpdateVersion = availableUpdates.Max(manifest => manifest.Version).ToString(),
                }, JsonRequestBehavior.AllowGet);
            }
            catch
            {

            }
            return Json(new
            {
                AvailableUpdate = false,
                HighestServerify = UpdateSeverity.None.ToString(),
                UpdateVersion = "",
            }, JsonRequestBehavior.AllowGet);
        }

        public async Task<ActionResult> Update()
        {
            return View(await GetAvailableUpdates());
        }

        [HttpPost, ActionName("Update")]
        public async Task<ActionResult> UpdateConfirmed(string version)
        {
            var updateClient = new UpdateClient(this.Request.ServerVariables["SERVER_NAME"],
                Configuration.PackageUpdateServiceLocation);
            var updateManifest = await updateClient.GetUpdateVersionAsync(UpdateClient.Components.ManagementConsole, version);
            if (updateManifest == null)
            {
                return HttpNotFound();
            }
            var package = updateManifest.GetPackage(FilePackage.PackageNameConsole);
            if (package == null)
            {
                return HttpNotFound();
            }
            var msDeployPackage = package.FindFileByExtension(".zip");
            var msDeployParameters = package.FindFileByExtension(".xml");
            if (msDeployPackage == null || msDeployParameters == null)
            {
                return HttpNotFound();
            }
            try
            {
                string localPackageLocation = await updateClient.DownloadPackageFileToTempFileAsync(UpdateClient.Components.ManagementConsole, updateManifest, package, msDeployPackage);
                string parametersFile = String.Empty;
                if (!String.IsNullOrWhiteSpace(localPackageLocation))
                {
                    using (var parametersStream = await updateClient.DownloadPackageFileToTempFileStreamAsync(UpdateClient.Components.ManagementConsole, updateManifest, package, msDeployParameters))
                    {
                        var parametersDoc = XDocument.Load(parametersStream);
                        var parameters = parametersDoc.Element("parameters")
                            .Elements()
                            .ToDictionary(element => element.Attribute("name").Value, element => element.Attribute("value"), StringComparer.OrdinalIgnoreCase);
                        // Process specific parameters that we know about
                        parameters["AD_RealmAppSetting"].Value = "1";
                        parameters["AD_AudienceUriAppSetting"].Value = "1";
                        parameters["AD_ClientID"].Value = Configuration.ClientID;
                        parameters["AD_ClientPassword"].Value = Configuration.ClientKey;
                        parameters["AD_APPIDUri"].Value = Url.Action("Index", "Home", null, "http");
                        parameters["ConsoleContext-Web.config Connection String"].Value = Configuration.ConsoleConnectionString;
                        parameters["AD_Tenant"].Value = Configuration.Tenant;
                        // Process all other parameters that we don't need specific handling for. ie. there's a 1:1 mapping between parameter & appSetting
                        foreach (var setting in ConfigurationManager.AppSettings.AllKeys
                                                    .Where(setting => parameters.ContainsKey(setting)))
                        {
                            parameters[setting].Value = ConfigurationManager.AppSettings[setting];
                        }
                                                    
                        parametersStream.Seek(0, SeekOrigin.Begin);
                        parametersStream.SetLength(0);
                        parametersDoc.Save(parametersStream);

                        parametersFile = ((FileStream)parametersStream).Name;
                    }
                }

                var msdeploy = new Process();
                msdeploy.StartInfo.FileName = Environment.ExpandEnvironmentVariables("%systemdrive%\\Program Files\\IIS\\Microsoft Web Deploy V3\\msdeploy.exe");
                string sitePath = Environment.ExpandEnvironmentVariables("%systemdrive%\\inetpub\\wwwroot");
                if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DEPLOYMENT_TARGET")))
                {
                    sitePath = Environment.ExpandEnvironmentVariables("%DEPLOYMENT_TARGET%");
                }
                else if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("HOME")))
                {
                    sitePath = Environment.ExpandEnvironmentVariables("%HOME%\\site\\wwwroot");
                }
                else
                {
                    Trace.TraceInformation("DEPLOYMENT_TARGET or HOME environment variables not set. Maybe not running in WAWS?");
                }
                msdeploy.StartInfo.Arguments = String.Format("-verb:sync -useCheckSum -source:package='{0}' -dest:contentPath='{1}' -setParamFile={2}", 
                    localPackageLocation, sitePath, parametersFile);
                msdeploy.StartInfo.UseShellExecute = false;
                msdeploy.StartInfo.RedirectStandardOutput = true;
                msdeploy.StartInfo.RedirectStandardError = true;
                msdeploy.Start();
                return Json(new { Output = msdeploy.StandardOutput.ReadToEnd(), Error = msdeploy.StandardError.ReadToEnd() });
            }
            catch (Exception ex)
            {
                return Json(new { Output = "", Error = ex.ToString() });
            }
        }

        async Task<IEnumerable<PackageManifest>> GetAvailableUpdates()
        {
            var updateClient = new UpdateClient(this.Request.ServerVariables["SERVER_NAME"],
                Configuration.PackageUpdateServiceLocation);
            var currentVersion = Assembly.GetAssembly(typeof(HomeController)).GetName().Version;
            return (await updateClient.GetAvailableManifestsAsync(UpdateClient.Components.ManagementConsole))
                .Where(manifest => manifest.Version > currentVersion)
                .ToList();
        }

        public ActionResult SignIn()
        {
            var config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
            var signInMessage = new SignInRequestMessage(new Uri(config.Issuer), config.Realm, Url.Action("Index", "Home", null, Request.Url.Scheme));
            return new RedirectResult(signInMessage.WriteQueryString());
        }

        public ActionResult SignOut()
        {
            FederatedAuthentication.SessionAuthenticationModule.SignOut();
            Session.ResetAllOAuthInformation();

            // TODO: Re-enable ws-fed signout logic when eSTS supports signout across all protocols (incl. OAuth2). In the interim, use the OpenID Connect endpoints
            //WsFederationConfiguration config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
            //SignOutRequestMessage signoutMessage = new SignOutRequestMessage(new Uri(config.Issuer), Url.Action("Index", "Home", null, Request.Url.Scheme));
            //signoutMessage.SetParameter("wtrealm", config.Realm);
            //return new RedirectResult(signoutMessage.WriteQueryString());
            return new RedirectResult(String.Format("{0}{1}/oauth2/logout?post_logout_redirect_uri={2}", 
                AzureEnvironment.AadLoginEndpoint,
                Configuration.Tenant,
                Url.Action("Index", "Home", null, Request.Url.Scheme)));
        }

        public ActionResult AuthorizationCode(string code, string state, string error, string error_description)
        {
            if (Session.IsValidAuthorizationState(state))
            { 
                if (!String.IsNullOrWhiteSpace(code))
                {
                    Session.GetAccessToken(state, code);
                }
                else
                {
                    Session.AuthorizationCodeFailed(state, error, error_description);
                }
                return Session.RedirectToAuthenticationInitiator(state);
            }
            return new HttpUnauthorizedResult();
        }

        public ActionResult Start()
        {
            // Force the realm to http for backward compatibility
            string ourRealm = Url.Action("Index", "Home", null, "http");
            var wsFedConfig = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
            if (!String.Equals(wsFedConfig.Realm, ourRealm, StringComparison.OrdinalIgnoreCase) || 
                !String.Equals(wsFedConfig.Issuer, AzureEnvironment.WsFedIssuerCommonEndpoint, StringComparison.OrdinalIgnoreCase))
            {
                var config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~/");
                var identityModelSection = config.GetSection(SystemIdentityModelServicesSection.SectionName) as SystemIdentityModelServicesSection;
                var federationConfig = identityModelSection.FederationConfigurationElements.Cast<FederationConfigurationElement>().FirstOrDefault();
                federationConfig.WsFederation.Realm = ourRealm;
                federationConfig.WsFederation.Issuer = AzureEnvironment.WsFedIssuerCommonEndpoint;
                config.Save();
            }

            return View(new Startup
                {
                    ClientId = Configuration.ClientID,
                    ClientKey = Configuration.ClientKey,
                });
        }

        [HttpPost]
        public ActionResult Start(Startup model)
        {
            if (ModelState.IsValid && Request.IsAuthenticated && User is ClaimsPrincipal)
            {
                string tenantId = String.Empty;
                var tenantClaim = ((ClaimsPrincipal)User).FindFirst("http://schemas.microsoft.com/identity/claims/tenantid");
                if (tenantClaim != null)
                {
                    tenantId = tenantClaim.Value;
                }
                Configuration.Tenant = tenantId;
                Configuration.ClientID = model.ClientId;
                Configuration.ClientKey = model.ClientKey;
                MvcApplication.RefreshValidationSettings(tenantId);
                Configuration.IsAppInitialized = true;
                return RedirectToAction("Index");
            }
            return View(model);
        }
    }
}